<template>
  <div>
    <el-upload
      class="upload-demo"
      drag
      action="#"
      :http-request="handleHttpRequest"
      :on-remove="handleRemoveFile"
      :on-success="handleSuccess"
      multiple>
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
    </el-upload>
  </div>
</template>

<script>
  import {createTask, merge, preSignUrl, taskInfo} from '@/api/admin/file';
  import md5 from "@/util/md5";
  import Queue from 'promise-queue-plus';

  export default {
    name: "fragmentationUpload",
    props: {
      chunkSize: {
        type: Number,
        default: 5 * 1024 * 1024
      }
    },
    data() {
      return {
        upload: {
          open: false,
          isUploading: false
        },
        errorVisible: false,
        errorData: [],
        fileUploadChunkQueue: {},
      };
    },

    methods: {
      async getTaskInfo(file) {
        let task;
        const identifier = await md5(file);
        const {data} = await taskInfo(identifier);
        if (data && data.code === 200) {
          task = data.data;
          if (!task) {
            const initTaskData = {
              identifier,
              original: file.name,
              fileSize: file.size,
              chunkSize: this.chunkSize
            };
            const {data} = await createTask(initTaskData);
            if (data && data.code === 200) {
              task = data.data
            } else {
              this.$message.error('文件上传错误')
            }
          }
        } else {
          this.$message.error('文件上传错误')
        }
        return task
      },
      async handleHttpRequest(options) {
        const file = options.file;
        const task = await this.getTaskInfo(file);
        if (task) {
          const {finished, path, taskRecord} = task;
          const { identifier} = taskRecord;
          if (finished) {
            return path
          } else {
            const errorList = await this.handleUpload(file, taskRecord, options);
            if (errorList.length > 0) {
              this.$message.error('部分分片上次失败，请尝试重新上传文件')
              return;
            }
            const {data} = await merge(identifier);
            if (data && data.code === 200) {
              return path;
            } else {
              this.$message.error('文件上传错误')
            }
          }
        } else {
          this.$message.error('获取上传任务失败，请尝试重新上传文件')
        }
      },
      handleUpload(file, taskRecord, options) {
        let lastUploadedSize = 0; // 上次断点续传时上传的总大小
        let uploadedSize = 0 // 已上传的大小
        const totalSize = file.size || 0 // 文件总大小
        let startMs = new Date().getTime(); // 开始上传的时间
        const {exitPartList, chunkSize, chunkNum, identifier} = taskRecord

        // 获取从开始上传到现在的平均速度（byte/s）
        const getSpeed = () => {
          // 已上传的总大小 - 上次上传的总大小（断点续传）= 本次上传的总大小（byte）
          const intervalSize = uploadedSize - lastUploadedSize
          const nowMs = new Date().getTime()
          // 时间间隔（s）
          const intervalTime = (nowMs - startMs) / 1000
          return intervalSize / intervalTime
        };

        const uploadNext = async (partNumber) => {
          const start = new Number(chunkSize) * (partNumber - 1)
          const end = start + new Number(chunkSize)
          const blob = file.slice(start, end)
          const {data} = await preSignUrl({identifier: identifier, partNumber: partNumber})
          if (data && data.code === 200) {
            //这里不能走全局过拦截器，会导致minio无法上传
            await axios.create().request({
              url: data.data,
              method: 'PUT',
              data: blob,
              headers: {'Content-Type': 'application/octet-stream'}
            });
            return Promise.resolve({partNumber: partNumber, uploadedSize: blob.size})
          }
          return Promise.reject(`分片${partNumber}， 获取上传地址失败`)
        }

        /**
         * 更新上传进度
         * @param increment 为已上传的进度增加的字节量
         */
        const updateProcess = (increment) => {
          increment = new Number(increment)
          const {onProgress} = options;
          let factor = 1000; // 每次增加1000 byte
          let from = 0;
          // 通过循环一点一点的增加进度
          while (from <= increment) {
            from += factor;
            uploadedSize += factor;
            const percent = Math.round(uploadedSize / totalSize * 100).toFixed(2);
            if (percent>100){
              onProgress({percent: 100})
            }else {
              onProgress({percent: percent})
            }
          }

          const speed = getSpeed();
          const remainingTime = speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : '未知';
          console.log('剩余大小：', (totalSize - uploadedSize) / 1024 / 1024, 'mb');
          console.log('当前速度：', (speed / 1024 / 1024).toFixed(2), 'mbps');
          console.log('预计完成：', remainingTime);
        }


        return new Promise(resolve => {
          const failArr = [];
          const queue = Queue(5, {
            "retry": 0,               //Number of retries
            "retryIsJump": false,     //retry now?
            "workReject": function (reason, queue) {
              failArr.push(reason)
            },
            "queueEnd": function (queue) {
              resolve(failArr);
            }
          });
          this.fileUploadChunkQueue[file.uid] = queue;
          for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {
            const exitPart = (exitPartList || []).find(exitPart => exitPart.partNumber == partNumber)
            if (exitPart) {
              // 分片已上传完成，累计到上传完成的总额中,同时记录一下上次断点上传的大小，用于计算上传速度
              lastUploadedSize += new Number(exitPart.size)
              updateProcess(exitPart.size)
            } else {
              queue.push(() => uploadNext(partNumber).then(res => {
                // 单片文件上传完成再更新上传进度
                updateProcess(res.uploadedSize)
              }))
            }
          }
          if (queue.getLength() == 0) {
            // 所有分片都上传完，但未合并，直接return出去，进行合并操作
            resolve(failArr);
            return;
          }
          queue.start()
        })
      },
      handleRemoveFile(uploadFile, uploadFiles) {
        const queueObject = this.fileUploadChunkQueue[uploadFile.uid]
        if (queueObject) {
          queueObject.stop();
          this.fileUploadChunkQueue[uploadFile.uid] = undefined
        }
      },
      handleSuccess(response, file, fileList){
        this.$emit('on-success',response, file, fileList);
      }
    }
  }
</script>

<style scoped>

</style>
